home *** CD-ROM | disk | FTP | other *** search
/ NOVA - For the NeXT Workstation / NOVA - For the NeXT Workstation.iso / SourceCode / AdobeExamples / NX_Clock / ClockView.m < prev    next >
Text File  |  1992-12-19  |  19KB  |  734 lines

  1.  
  2. /*
  3.  * (a)  (C) 1990 by Adobe Systems Incorporated. All rights reserved.
  4.  *
  5.  * (b)  If this Sample Code is distributed as part of the Display PostScript
  6.  *    System Software Development Kit from Adobe Systems Incorporated,
  7.  *    then this copy is designated as Development Software and its use is
  8.  *    subject to the terms of the License Agreement attached to such Kit.
  9.  *
  10.  * (c)  If this Sample Code is distributed independently, then the following
  11.  *    terms apply:
  12.  *
  13.  * (d)  This file may be freely copied and redistributed as long as:
  14.  *    1) Parts (a), (d), (e) and (f) continue to be included in the file,
  15.  *    2) If the file has been modified in any way, a notice of such
  16.  *      modification is conspicuously indicated.
  17.  *
  18.  * (e)  PostScript, Display PostScript, and Adobe are registered trademarks of
  19.  *    Adobe Systems Incorporated.
  20.  * 
  21.  * (f) THE INFORMATION BELOW IS FURNISHED AS IS, IS SUBJECT TO
  22.  *    CHANGE WITHOUT NOTICE, AND SHOULD NOT BE CONSTRUED
  23.  *    AS A COMMITMENT BY ADOBE SYSTEMS INCORPORATED.
  24.  *    ADOBE SYSTEMS INCORPORATED ASSUMES NO RESPONSIBILITY
  25.  *    OR LIABILITY FOR ANY ERRORS OR INACCURACIES, MAKES NO
  26.  *    WARRANTY OF ANY KIND (EXPRESS, IMPLIED OR STATUTORY)
  27.  *    WITH RESPECT TO THIS INFORMATION, AND EXPRESSLY
  28.  *    DISCLAIMS ANY AND ALL WARRANTIES OF MERCHANTABILITY, 
  29.  *    FITNESS FOR PARTICULAR PURPOSES AND NONINFRINGEMENT
  30.  *    OF THIRD PARTY RIGHTS.
  31.  */
  32.  
  33. /*
  34. *    ClockView.m
  35. *
  36. *    This class handles the drawing of the clock and the moving of the alarm.
  37. *    The clock face is drawn into a bitmap and then composited into the buffered
  38. *    window before drawing the hands. The hands are stored in the server
  39. *    as user paths. Each hand also has a graphic state associated with it. Before
  40. *    hand is drawn, its graphic state is installed and then rotated to its current
  41. *    angle and then the user path is rendered.
  42. *
  43. *    An animator object has been borrowed from the stopwatch implementation in
  44. *    the NeXTDeveloper directory. This object makes adjustments to the timed
  45. *    entry in order to keep the timing up to date.
  46.  *
  47.  *    Version:    2.0
  48.  *    Author:    Ken Fromm
  49.  *    History:
  50.  *            03-07-91        Added this comment.
  51. */
  52.  
  53. #import "Animator.h"
  54. #import "ClockView.h"
  55. #import "ClockViewWraps.h"
  56.  
  57. #import <appkit/Cell.h>
  58. #import <appkit/Control.h>
  59. #import <appkit/NXImage.h>
  60. #import <appkit/View.h>
  61. #import <appkit/nextstd.h>
  62.  
  63. #import <dpsclient/dpsclient.h>
  64. #import <dpsclient/wraps.h>
  65.  
  66. @implementation ClockView
  67.  
  68. static void drawClockHand(id self, int hand);
  69.  
  70. /*
  71. *  These are the user path operands and operators for the clock hands.
  72. *  They are sent and stored in the server.
  73. */
  74. static float ptsHour[] = { -10, -10, 10, 170, -4.5, 0, 0, 120, 0,120, 4.5, 180, 0, 0, -120,
  75.         0, 0, 0, 0, 10, 360, 0};
  76. static char opsHour[] = {dps_setbbox, dps_moveto, dps_rlineto, dps_arcn, dps_rlineto,
  77.         dps_closepath, dps_moveto, dps_arcn, dps_closepath};
  78. static float ptsMin[] = { -10, -10, 10, 175, -4.5, 0, 0, 162, 0,162, 4.5, 180, 0, 0, -162,
  79.         0, 0, 0, 0, 10, 360, 0};
  80. static char opsMin[] = {dps_setbbox, dps_moveto, dps_rlineto, dps_arcn, dps_rlineto, 
  81.         dps_closepath, dps_moveto, dps_arcn, dps_closepath};
  82. static float ptsSec[] = { -10, -30, 10, 170, -1.5, 0, 0, 145, 3, 0, 0, -145,
  83.         4, 0, 0, -20, 0, -20, 5.5, 360, 180, 0, 20, 4, 0, 0, 0, 0, 0, 10, 360, 0};
  84. static char opsSec[] = {dps_setbbox, dps_moveto, dps_rlineto, dps_rlineto, dps_rlineto,
  85.         dps_rlineto, dps_rlineto, dps_arcn, dps_rlineto, dps_rlineto, dps_closepath,
  86.         dps_moveto, dps_arcn, dps_closepath};
  87.  
  88. static float ptsAlarmTop[] = { -5, 70, 5, 120, -1.0, 100, 0, 5, 0, 105, 1.0, 180, 0, 0, -5};
  89. static char opsAlarmTop[] = {dps_setbbox, dps_moveto, dps_rlineto, dps_arcn, dps_rlineto,
  90.         dps_closepath};
  91. static float ptsAlarmBot[] = { -5, -2, 5, 120, -1.0, 0, 0, 100, 2.0, 0, 0, -100};
  92. static char opsAlarmBot[] = {dps_setbbox, dps_moveto, dps_rlineto, dps_rlineto,
  93.         dps_rlineto, dps_closepath};
  94.  
  95. /* 
  96. *     Initialize the instance variables and create an Animator object.
  97. *     The animator is used to keep the timed_entry up to date. Without
  98. *    the animator adjustments, the clock loses time. An array to hold
  99. *    the hit detection user path is allocated. This mouse location
  100. *    will be inserted into this user path before the hit detection is
  101. *    tested for.
  102. */
  103. - initFrame:(const NXRect *) frameRect
  104. {    
  105.     [super initFrame:frameRect]; 
  106.     [self setClipping:NO];
  107.  
  108.     gstatesOn = upathsServer = YES;
  109.     totalTime = numIterations = 0;
  110.     angleAlarm = 0;
  111.     gstateHour = gstateMin = gstateSec = gstateShad = 0;
  112.     upathHour = upathMin = upathSec = upathAlarmTop = upathAlarmBot = 0;
  113.  
  114.     NX_MALLOC(hitPoint.pts, float, MAX_PTS_HIT);
  115.     NX_MALLOC(hitPoint.ops, char, MAX_OPS_HIT);
  116.     [self initializeHitPoint];
  117.  
  118.     animatorId = [Animator  newChronon: 1.0
  119.               adaptation: 3.0                
  120.               target: self
  121.               action: @selector(tick:)
  122.               autoStart: NO
  123.               eventMask: NX_ALLEVENTS];
  124.  
  125.     return self;
  126. }
  127.  
  128. /* Most of these will remain the same between hit detection tests. */
  129. - initializeHitPoint
  130. {
  131.     int        i;
  132.  
  133.     for (i = 0; i < MAX_PTS_HIT; i++)
  134.     {
  135.         hitPoint.pts[i] = 0;            
  136.     }
  137.     hitPoint.num_pts = i;
  138.      
  139.     hitPoint.ops[0] = dps_setbbox;
  140.     hitPoint.ops[1] = dps_moveto;
  141.     hitPoint.ops[2] = dps_rlineto;
  142.     hitPoint.ops[3] = dps_rlineto;
  143.     hitPoint.ops[4] = dps_rlineto;
  144.     hitPoint.ops[5] = dps_closepath;
  145.     hitPoint.num_ops = 6;
  146.  
  147.     return self;
  148. }
  149.  
  150. - free
  151. {
  152.     if (hitPoint.pts)
  153.         NX_FREE(hitPoint.pts);
  154.     if (hitPoint.ops)
  155.         NX_FREE(hitPoint.ops);
  156.  
  157.     [animatorId free]; 
  158.     [imageId  free];
  159.  
  160.     return [super  free];
  161. }
  162.  
  163. - setDisplayTime:anObject
  164. {
  165.     displayTime = anObject;
  166.  
  167.     return self;
  168. }
  169.  
  170. - toggleGstate:sender
  171. {
  172.     totalTime = numIterations = 0;
  173.     gstatesOn = [sender state];
  174.  
  175.     return self;
  176. }
  177.  
  178. - toggleUpath:sender
  179. {
  180.     totalTime = numIterations = 0;
  181.     upathsServer = [sender state];
  182.  
  183.     return self;
  184. }
  185.  
  186. /* Use DPSDoUserPath to draw the lines of the clock. */
  187. static void drawUpathLines (pts, ops, clr, wid, x, y, startlen, endlen, deg)
  188.     float        pts[];
  189.     char        ops[];
  190.     float     clr, wid, x, y, startlen, endlen, deg;
  191. {
  192.     int        i , j;
  193.     
  194.     float        angle;
  195.     
  196.     deg = ABS(deg * RADIAN);
  197.     i = 4; j = 1;
  198.     for (angle = 0; angle < 2 * M_PI; angle += deg)
  199.     {
  200.         pts[i++] = (floor) (x + (float) cos(angle) * startlen);
  201.         pts[i++] = (floor) (y + (float) sin(angle) * startlen);
  202.         ops[j++] = dps_moveto;
  203.         
  204.         pts[i++] = (floor) (x + (float) cos(angle) * endlen);
  205.         pts[i++] = (floor) (y + (float) sin(angle) * endlen);
  206.         ops[j++] = dps_lineto;
  207.     }
  208.  
  209.     PSsetgray(clr);
  210.     PSsetlinewidth(wid);
  211.     DPSDoUserPath(&pts[4], i - 4, dps_float, &ops[1], j -1, pts, dps_ustroke);
  212. }
  213.  
  214. /*
  215. *    Draw the clock face into an offscreen bitmap. Resizes the bitmap if the
  216. *    frame of this view is larger than the current size of the bitmap. Uses the
  217. *    frame dimensions instead of the bounds because the bounds are 
  218. *    affected by the scale:: method and do not produce the correct
  219. *    dimensions in the default user space.
  220. */
  221. - drawFace
  222. {    
  223.     char            *ops;
  224.  
  225.     float            *pts;
  226.  
  227.     NXSize        size;
  228.  
  229.     NXPoint        center;
  230.  
  231.     float            maxnums, maxdashes, maxcircle;
  232.  
  233.     if (imageId)
  234.     {
  235.         [imageId  getSize:&size];
  236.         if (size.width < frame.size.width || size.height < frame.size.height)
  237.             [imageId  setSize:&frame.size];
  238.     }
  239.     else
  240.         imageId = [[NXImage newSize:&frame.size]  setFlipped:NO];
  241.  
  242.     center.x =  floor(bounds.size.width/2);
  243.     center.y = floor(bounds.size.height/2);
  244.     maxcircle = MIN(center.y - 10, center.x -10);
  245.     maxnums = maxcircle * SIZENUMS;    
  246.     maxdashes = maxcircle * SIZEDASHES;
  247.                     
  248.     NX_MALLOC(pts, float, MAX_PTS); 
  249.     NX_MALLOC(ops, char, MAX_OPS);
  250.     
  251.     pts[0] = bounds.origin.x;
  252.     pts[1] = bounds.origin.y;
  253.     pts[2] = bounds.origin.x + bounds.size.width;
  254.     pts[3] = bounds.origin.y + bounds.size.height;
  255.     ops[0] = dps_setbbox;
  256.  
  257.     [imageId lockFocus];
  258.         PSscale(frame.size.width/bounds.size.width, frame.size.height/bounds.size.height);
  259.         PSWEraseView (CLRVIEW, bounds.origin.x, bounds.origin.y,
  260.             bounds.size.width, bounds.size.height);
  261.         PSWMakeCircle(center.x, center.y, maxcircle);
  262.         PSWFillPath(CLRCIRC);
  263.         
  264.         PSsetlinecap(1);
  265.         drawUpathLines(pts, ops, CLRMIN, WIDMIN, center.x, center.y,
  266.             maxdashes * LENMIN, maxdashes, DEGMIN);
  267.         drawUpathLines(pts, ops, CLRHOUR, WIDHOUR, center.x, center.y,
  268.             maxdashes * LENHOUR, maxdashes, DEGHOUR);
  269.     [imageId unlockFocus];
  270.     
  271.     if (pts)
  272.         NX_FREE(pts);
  273.     if (ops)
  274.         NX_FREE(ops);
  275.  
  276.     return self;
  277. }
  278.  
  279. /* Define the userpaths of the hands as user objects. */
  280. - defineUPaths
  281. {
  282.     /* Setup hour hand upath. */    
  283.     PSWSetUpath(ptsHour, sizeof (ptsHour)/sizeof (float),
  284.         opsHour, sizeof (opsHour)/sizeof (char));
  285.     upathHour = DPSDefineUserObject(0);
  286.  
  287.     /* Setup minute hand upath. */
  288.     PSWSetUpath(ptsMin, sizeof (ptsMin)/sizeof (float),
  289.         opsMin, sizeof (opsMin)/sizeof (char));
  290.     upathMin = DPSDefineUserObject(0);
  291.  
  292.     /* Setup seconds hand upath. */
  293.     PSWSetUpath(ptsSec, sizeof (ptsSec)/sizeof (float),
  294.         opsSec, sizeof (opsSec)/sizeof (char));
  295.     upathSec = DPSDefineUserObject(0);
  296.  
  297.     /* Setup top of alarm hand upath. */
  298.     PSWSetUpath(ptsAlarmTop, sizeof (ptsAlarmTop)/sizeof (float),
  299.         opsAlarmTop, sizeof (opsAlarmTop)/sizeof (char));
  300.     upathAlarmTop = DPSDefineUserObject(0);
  301.  
  302.     /* Setup bottom of alarm hand upath. */
  303.     PSWSetUpath(ptsAlarmBot, sizeof (ptsAlarmBot)/sizeof (float),
  304.         opsAlarmBot, sizeof (opsAlarmBot)/sizeof (char));
  305.     upathAlarmBot = DPSDefineUserObject(0);
  306.  
  307.     return self;
  308. }
  309.  
  310. /*
  311.  * If a user object has not been allocated, then a gstate has also not been 
  312.  * allocated. As a result, create a gstate before defining the user object.
  313.  * If a user object exists, then copy the new gstate into the old
  314.  * structure. No need to redefine the user object because
  315.  * it still refers to the same structure. The PSpop() pops the result of
  316.  * PScurrentgstate() off of the stack.
  317.  */  
  318. static int  definegstate(gstate, offsetx, offsety, color, linewidth)
  319.     int            gstate;
  320.     float            offsetx, offsety, color, linewidth;
  321. {
  322.     PSgsave();
  323.     PSWSetGstate(offsetx, offsety, color, linewidth);
  324.     if (!gstate)
  325.     {
  326.         PSgstate();
  327.         gstate = DPSDefineUserObject(gstate);
  328.     }
  329.     else
  330.     {
  331.         PScurrentgstate(gstate);
  332.         PSpop();
  333.     }
  334.     PSgrestore();
  335.  
  336.     return gstate;
  337. }
  338.  
  339. /*
  340. *    Redefine the gsates because the CTM has changed as the result
  341. *    of the scale:: method.
  342. */
  343. - defineGStates
  344. {
  345.     float                angle;
  346.  
  347.     NXPoint            center;
  348.  
  349.     struct timeval        timeofDay;
  350.     struct tm            *localTime;
  351.  
  352.     if (window)
  353.     {
  354.         center.x =  floor(bounds.size.width/2);
  355.         center.y = floor(bounds.size.height/2);
  356.  
  357.         [[animatorId startEntry] resetRealTime]; 
  358.  
  359.         [self  lockFocus];
  360.             gettimeofday(&timeofDay, NULL);
  361.             localTime = localtime(&timeofDay.tv_sec);
  362.  
  363.             angleHour = ((localTime->tm_hour % 12) + localTime->tm_min/60.0) * DEGHOUR;
  364.             gstateHour = definegstate (gstateHour, center.x, center.y,
  365.             CLRHANDS - 0.2, LNWIDHANDS);
  366.  
  367.             angleMin = (localTime->tm_min + localTime->tm_sec/60.0) * DEGMIN;
  368.             gstateMin = definegstate(gstateMin, center.x + OFFSETHANDSX,
  369.                 center.y + OFFSETHANDSY, CLRHANDS - 0.2, LNWIDHANDS);
  370.  
  371.             angleSec = localTime->tm_sec * DEGMIN;
  372.                 gstateSec = definegstate(gstateSec,
  373.                 center.x + (2 * OFFSETHANDSX),
  374.                 center.y + (2 * OFFSETHANDSY),
  375.                 CLRSECOND, LNWIDSECOND);
  376.  
  377.             gstateShad = definegstate(gstateShad,
  378.                 center.x + (2 * OFFSETHANDSX) + OFFSETSHADX,
  379.                 center.y + (2 * OFFSETHANDSY) + OFFSETSHADY,
  380.                 CLRSHADOW, LNWIDSECOND);
  381.         [self  unlockFocus];
  382.     }
  383.  
  384.     return self;
  385. }
  386.  
  387. /*
  388. *    This method changes the title of the menu cell according to the
  389. *     value of the trace variable.
  390. */
  391. -trace:sender
  392. {
  393.     trace = YES;
  394.     
  395.     return self;
  396. }
  397.  
  398. /* Messaged by the Animator object after a timed entry has been received. */
  399. - tick:sender
  400. {
  401.     angleSec = angleSec +     TICKSEC;
  402.     angleMin = angleMin + TICKMIN;
  403.     angleHour =  angleHour + TICKHOUR;
  404.  
  405.     [self display];
  406.  
  407.     return self;
  408. }
  409.  
  410. /*
  411. *  Scales the view and then redefines the graphic states.
  412. *  The graphic state objects pick up the scaled view upon redefinition.
  413. */
  414. - sizeTo:(NXCoord)width :(NXCoord)height
  415. {    
  416.     NXRect        xframe;
  417.  
  418.     [animatorId stopEntry]; 
  419.     
  420.     xframe = frame;
  421.     [super sizeTo:width :height];
  422.  
  423.     if (xframe.size.width && xframe.size.height)
  424.     {    
  425.         [self scale:width/xframe.size.width :height/xframe.size.height];
  426.         [self  drawFace];
  427.         [self  defineGStates];
  428.     }
  429.     
  430.     return self;
  431. }
  432.  
  433. /* Enter a modal loop to redraw the alarm hand per mouse drag event. */
  434. - setAlarm:(NXEvent *)event
  435. {
  436.     int            old_mask;
  437.  
  438.     NXPoint        p, center;
  439.     
  440.       NXEvent        peek;
  441.  
  442.     center.x =  floor(bounds.size.width/2);
  443.     center.y = floor(bounds.size.height/2);
  444.  
  445.     old_mask = [window addToEventMask:NX_MOUSEUPMASK|
  446.                 NX_MOUSEDRAGGEDMASK|NX_TIMERMASK];
  447.     event = [NXApp getNextEvent:NX_MOUSEUPMASK|
  448.                 NX_MOUSEDRAGGEDMASK|NX_TIMERMASK];
  449.  
  450.     [self  lockFocus];
  451.         while (event->type != NX_MOUSEUP)
  452.         {
  453.             if (event->type == NX_TIMER)
  454.             {
  455.                 angleSec = angleSec + TICKSEC;
  456.                 angleMin = angleMin + TICKMIN;
  457.                 angleHour = angleHour + TICKHOUR;
  458.                 if (![NXApp  peekNextEvent:NX_MOUSEDRAGGEDMASK into:&peek])
  459.                     event = [NXApp getNextEvent:NX_MOUSEDRAGGEDMASK];
  460.             }
  461.  
  462.             if (event->type == NX_MOUSEDRAGGED)
  463.             {
  464.                 p = event->location;        
  465.                 [self convertPoint:&p fromView:nil];
  466.                 angleAlarm = atan((p.y - center.y)/(p.x - center.x))/RADIAN - 90;
  467.                 if (p.x - center.x < 0)
  468.                     angleAlarm -= 180;
  469.                 if ([NXApp  peekNextEvent:NX_TIMERMASK into:&peek
  470.                         waitFor:0  threshold:NX_BASETHRESHOLD])
  471.                 {
  472.                     angleSec = angleSec + TICKSEC;
  473.                     angleMin = angleMin + TICKMIN;
  474.                     angleHour = angleHour + TICKHOUR;
  475.                     event = [NXApp getNextEvent:NX_TIMERMASK];
  476.                 }
  477.             }
  478.  
  479.             PSgsave();
  480.                 [self  drawSelf:&bounds :1];
  481.             PSgrestore();
  482.  
  483.             [window flushWindow];
  484.             NXPing();
  485.  
  486.             event = [NXApp getNextEvent:NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK|NX_TIMERMASK
  487.                         waitFor:1000  threshold:NX_BASETHRESHOLD];
  488.         }
  489.  
  490.     [window setEventMask:old_mask];
  491.  
  492.     return self;
  493. }
  494.  
  495. /* Set the hit detection userpath upon a mouse down. */
  496. - setHitPoint:(const NXPoint *)p
  497. {
  498.     float            z;
  499.  
  500.     NXPoint        pt;
  501.   
  502.     pt.x = p->x - floor(bounds.size.width/2);
  503.     pt.y = p->y - floor(bounds.size.height/2);
  504.  
  505.     z = sqrt (pt.x * pt.x  + pt.y * pt.y);
  506.     pt.x = pt.x - (z * sin(ABS(angleAlarm) * RADIAN));
  507.     pt.y = pt.y + z - z * cos(ABS(angleAlarm) * RADIAN);
  508.  
  509.     /*  Bounding Box */
  510.     hitPoint.pts[0] = floor(pt.x - HITSETTING/2);
  511.     hitPoint.pts[1] = floor(pt.y - HITSETTING/2);
  512.     hitPoint.pts[2] = ceil(pt.x + HITSETTING/2);
  513.     hitPoint.pts[3] = ceil(pt.y + HITSETTING/2);
  514.     
  515.     /*  Moveto */
  516.     hitPoint.pts[4] = pt.x - HITSETTING/2;
  517.     hitPoint.pts[5] = pt.y - HITSETTING/2;
  518.  
  519.     /* Rlineto's */
  520.     hitPoint.pts[7] = HITSETTING;
  521.     hitPoint.pts[8] = HITSETTING;
  522.     hitPoint.pts[11] = -HITSETTING;
  523.     
  524.     return self;
  525. }
  526.  
  527. /*
  528. *    Check for hit detection. No boundary check is made because the
  529. *    alarm hand can reside in pretty much the whole view.
  530. */
  531. - (BOOL) isHit:(const NXPoint *) p
  532.  {
  533.      int    hit;
  534.  
  535.     [self setHitPoint:p];
  536.     PSgsave();
  537.         PSWInstallGstate(gstateHour, angleAlarm);
  538.         PSWHitPath(upathAlarmTop, upathAlarmBot, hitPoint.pts,
  539.             hitPoint.num_pts, hitPoint.ops, hitPoint.num_ops, &hit);
  540.     PSgrestore();
  541.  
  542.     return (BOOL) hit;
  543. }
  544.  
  545. /*  This method handles a mouse down.  */
  546. - mouseDown:(NXEvent *)event
  547. {
  548.     NXPoint        p;
  549.   
  550.       p = event->location;
  551.     [self convertPoint:&p fromView:nil];
  552.  
  553.     if ([self isHit:&p])
  554.         [self setAlarm:event];
  555.  
  556.     return self;
  557. }
  558.  
  559. /*
  560. *    Either draws with gstates or doesn't. A slight performance advantage is
  561. *    gained with gstates but they use up an appreciable amount of memory
  562. *    so they should be used judiciously.
  563. */
  564. - setStateAndDraw
  565. {
  566.     NXPoint        center;
  567.  
  568.     if (gstatesOn)
  569.     {
  570.         PSWInstallGstate(gstateHour, angleAlarm);
  571.         drawClockHand(self, ALARM);
  572.  
  573.         PSWInstallGstate(gstateHour, angleHour);
  574.         drawClockHand(self, HOUR);
  575.  
  576.         PSWInstallGstate(gstateMin, angleMin);
  577.         drawClockHand(self, MINUTE);
  578.  
  579.         PSWInstallGstate(gstateShad, angleSec);
  580.         drawClockHand(self, SHADOW);
  581.  
  582.         PSWInstallGstate(gstateSec, angleSec);
  583.         drawClockHand(self, SECOND);
  584.     }
  585.     else
  586.     {
  587.         center.x =  floor(bounds.size.width/2);
  588.         center.y = floor(bounds.size.height/2);
  589.  
  590.         PSgsave();
  591.             PStranslate(center.x, center.y);
  592.             PSrotate(angleAlarm);
  593.             drawClockHand(self, ALARM);
  594.         PSgrestore();
  595.  
  596.         PSgsave();
  597.             PSsetgray(CLRHANDS - 0.2);
  598.             PSsetlinewidth(LNWIDHANDS);
  599.             PStranslate(center.x , center.y);
  600.             PSrotate(angleHour);
  601.             drawClockHand(self, HOUR);
  602.         PSgrestore();
  603.         
  604.         PSgsave();
  605.             PSsetgray(CLRHANDS - 0.2);
  606.             PSsetlinewidth(LNWIDHANDS);
  607.             PStranslate(center.x + OFFSETHANDSX,
  608.                 center.y + OFFSETHANDSY);
  609.             PSrotate(angleMin);
  610.             drawClockHand(self, MINUTE);
  611.         PSgrestore();
  612.  
  613.         PSgsave();
  614.             PSsetgray(CLRSHADOW);
  615.             PStranslate(center.x + (2*OFFSETHANDSX) + OFFSETSHADX,
  616.                 center.y + (2*OFFSETHANDSY) + OFFSETSHADY);
  617.             PSrotate(angleSec);
  618.             drawClockHand(self, SHADOW);
  619.         PSgrestore();
  620.  
  621.         PSgsave();
  622.             PSsetgray(CLRSECOND);
  623.             PSsetlinewidth(LNWIDSECOND);
  624.             PStranslate(center.x + OFFSETHANDSX + OFFSETSHADX,
  625.                 center.y + OFFSETHANDSY + OFFSETSHADY);
  626.             PSrotate(angleSec);
  627.             drawClockHand(self, SECOND);
  628.         PSgrestore();
  629.     }
  630. }
  631.  
  632. /*
  633. *    Draws the clock hands either as stored in the server
  634. *    or by sending them each time.
  635. */
  636. static void drawClockHand(id self, int hand)
  637. {
  638.     if (self->upathsServer)
  639.     {
  640.         switch(hand)
  641.         {
  642.             case ALARM:
  643.                 PSsetgray(CLRALARMTOP);
  644.                 PSWUpathFill(self->upathAlarmTop);
  645.                 PSsetgray(CLRALARMBOT);
  646.                 PSWUpathFill(self->upathAlarmBot);
  647.                 break;
  648.             case HOUR:
  649.                 PSWUpathStrokeFill(self->upathHour);
  650.                 break;            
  651.             case MINUTE:
  652.                 PSWUpathStrokeFill(self->upathMin);
  653.                 break;            
  654.             case SHADOW:
  655.                 PSWUpathFill(self->upathSec);
  656.                 break;            
  657.             case SECOND:
  658.                 PSWUpathFill(self->upathSec);
  659.                 PSWDrawCircle(CLRSECOND - 0.2);
  660.                 break;        
  661.         }
  662.     }
  663.     else
  664.     {
  665.         switch(hand)
  666.         {
  667.             case ALARM:
  668.                 PSsetgray(CLRALARMTOP);
  669.                 DPSDoUserPath(&ptsAlarmTop[4], sizeof (ptsAlarmTop)/sizeof (float) - 4,
  670.                     dps_float, &opsAlarmTop[1], sizeof (opsAlarmTop)/sizeof (char) -1,
  671.                     ptsAlarmTop, dps_ufill);
  672.                 PSsetgray(CLRALARMBOT);
  673.                 DPSDoUserPath(&ptsAlarmBot[4], sizeof (ptsAlarmBot)/sizeof (float) - 4,
  674.                     dps_float, &opsAlarmBot[1], sizeof (opsAlarmBot)/sizeof (char) - 1,
  675.                     ptsAlarmBot, dps_ufill);
  676.                 break;
  677.             case HOUR:
  678.                 DPSDoUserPath(&ptsHour[4], sizeof (ptsHour)/sizeof (float) - 4,
  679.                     dps_float, &opsHour[1], sizeof (opsHour)/sizeof (char) - 1,
  680.                     ptsHour, dps_ustroke);
  681.                 PSsetgray(CLRHANDS);
  682.                 DPSDoUserPath(&ptsHour[4], sizeof (ptsHour)/sizeof (float) - 4,
  683.                     dps_float, &opsHour[1], sizeof (opsHour)/sizeof (char) - 1,
  684.                     ptsHour, dps_ufill);
  685.                 break;
  686.             case MINUTE:
  687.                 DPSDoUserPath(&ptsMin[4], sizeof (ptsMin)/sizeof (float) - 4,
  688.                     dps_float, &opsMin[1], sizeof (opsMin)/sizeof (char) - 1,
  689.                     ptsMin, dps_ustroke);
  690.                 PSsetgray(CLRHANDS);
  691.                 DPSDoUserPath(&ptsMin[4], sizeof (ptsMin)/sizeof (float) - 4,
  692.                     dps_float, &opsMin[1], sizeof (opsMin)/sizeof (char) - 1,
  693.                     ptsMin, dps_ufill);
  694.                 break;
  695.             case SHADOW:
  696.                 DPSDoUserPath(&ptsSec[4], sizeof (ptsSec)/sizeof (float) - 4,
  697.                     dps_float, &opsSec[1], sizeof (opsSec)/sizeof (char) - 1,
  698.                     ptsSec, dps_ufill);
  699.                 break;        
  700.             case SECOND:
  701.                 DPSDoUserPath(&ptsSec[4], sizeof (ptsSec)/sizeof (float) - 4,
  702.                     dps_float, &opsSec[1], sizeof (opsSec)/sizeof (char) - 1,
  703.                     ptsSec, dps_ufill);
  704.                 PSWDrawCircle(CLRSECOND - 0.2);
  705.                 break;        
  706.         }
  707.     }
  708. }
  709.  
  710. - drawSelf:(NXRect *)r :(int) count
  711. {
  712.     int            ElapsedTime;
  713.  
  714.     [displayTime  setStringValue:""];
  715.  
  716.     PSWMarkTime (); NXPing ();        
  717.         if (trace)
  718.             DPSTraceContext(DPSGetCurrentContext(), YES);
  719.         [imageId composite:NX_COPY toPoint:&bounds.origin];
  720.         [self  setStateAndDraw];
  721.         if (trace)
  722.             DPSTraceContext(DPSGetCurrentContext(), NO);
  723.     PSWReturnTime (&ElapsedTime);
  724.  
  725.     trace = NO;
  726.     totalTime += ElapsedTime;
  727.     ++numIterations;
  728.     [displayTime setIntValue:(totalTime/numIterations)];
  729.  
  730.     return self;
  731. }
  732.  
  733. @end
  734.